﻿module app.controller.notify;
import hunt.application;
import std.experimental.logger;
import app.controller.base;
import app.exception;
import std.json;
import app.config;
import app.utils;
import std.array;
import std.string;
import app.helper.appinfo;
import app.exception;
import app.service.paybillservice;
import app.model;
import std.conv;

class NotifyController : BaseController
{
	private{
		string[string] channelConfig;
	}
	mixin MakeController;
	
	@Action
	void alipay()
	{
		runCatch(&_alipay,request);
	}	
	
	@Action
	void weixinpay()
	{
		runCatch(&_weixinpay,request);
	}
	
	
	//文档地址：https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.LTb29r&treeId=204&articleId=105301&docType=1
	private void _alipay(Request req)
	{
		checkChannel(req);
		
		version(Notify_Check_Sign){
			if(req.postForm is null)
				throwExceptionBuild!"EmptyRequest"("post empty data  : ");	
			auto forms = req.postForm.formMap;
			//第四步： 使用RSA的验签方法，通过签名字符串、签名参数（经过base64解码）及支付宝公钥验证签名。
			string toCheck = aaKsortSign(forms,["sign", "sign_type"],true);
			bool status = rsa256VerifyPublicKey(toCheck, req.post("sign"),channelConfig["rsa_public_key"]);
			if(status == false){
				throwExceptionBuild!"Sign"("check sign error sign type is : " ~ req.post("sign_type")~ " sign:"~ req.post("sign"));
			}
		}
		//检测
		PayBill bill = PayBillService.getInstance.getByWhere(["id" :req.post("out_trade_no")]);
		if(bill is null || req.post("trade_status") != "TRADE_SUCCESS" 
			|| channelConfig["appid"] != req.post("app_id") || bill.status != 0 
			|| bill.amount != cast(int)(to!float(req.post("total_amount")) * 100))
		{
			import std.conv;
			warning("alipay callback error post total amount:",  req.post!string("total_amount"));
			this.response.html("failed" );
			return;
		}
		
		updateBill(req.post("out_trade_no"), req.post("trade_no"));
		
		//放入队列
		auto jobid = putToJob(bill);
		
		trace("job id:", jobid, " bill id:", bill.id);
		this.response.html("success" );
	}
	
	
	
	private void _weixinpay(Request req)
	{
		checkChannel(req);
		
		import std.array, app.utils;
		Appender!(ubyte[]) buf = appender!(ubyte[]);
		req.Body.rest(0);
		req.Body.readAll((in ubyte[] data){buf.put(data);});
		string postXml =  cast(string)buf.data;
		info("WeixinPay Callback:", postXml);
		string[string] postHash = xml_aa_wx(postXml);
		
		assert("SUCCESS" == postHash["return_code"], "weixin callback error");
		
		version(Notify_Check_Sign){
			//第四步： 使用RSA的验签方法，通过签名字符串、签名参数（经过base64解码）及支付宝公钥验证签名。
			string toSign = aaKsortSign(postHash,["sign", "sign_type"],false);
			import std.string;
			toHexString(md5Of(toSign ~"&key=" ~channelConfig["key"])).idup.toUpper;
			if(toHexString(md5Of(toSign ~"&key=" ~channelConfig["key"])).idup.toUpper != postHash["sign"].toUpper){
				throwExceptionBuild!"Sign"("check sign error sign type is : md5  sign:"~ postHash["sign"]);
			}
		}
		
		
		//检测
		PayBill bill = PayBillService.getInstance.getByWhere(["id" :req.post("out_trade_no")]);
		if(bill is null || channelConfig["appid"] != postHash["appid"] || bill.status != 0 
			|| bill.amount != to!int(postHash["total_fee"]))
		{
			import std.conv;
			warning("weixin callback error post total amount:",  postHash);
			this.response.html(aa_xml(["return_code":"FAIL", "return_msg":"order check error"]) );
			return;
		}
		
		updateBill(postHash["out_trade_no"], postHash["transaction_id"]);
		
		//放入队列
		auto jobid = putToJob(bill);
		
		trace("job id:", jobid, " bill id:", bill.id);
		this.response.html(aa_xml(["return_code":"SUCCESS", "return_msg":"ok"]) );
	}
	
	private void checkChannel(Request req)
	{
		string[]appidChannel = req.getMate("appid").split("-");
		assert(appidChannel.length ==2, "callback url is error "~req.getMate("appid"));
		string appid = appidChannel[0];
		string channel = appidChannel[1];
		channelConfig = getAppChannelConf(appid, channel);
		if(channelConfig is null)
		{
			throwExceptionBuild!"NotSupportChannel"("NotSupportChannel : appid:"~ appid ~" channel:" ~channel);	
		}
	}
	
	auto putToJob(PayBill bill)
	{
		import app.utils, app.beanstalkd;
		Tube tube = getStalkdTube();
		Job job = new Job();
		job.append(to!string(bill.id));
		tube.put(job);
		return job.id;
	}
	
	void updateBill(string billId, string tradeNo)
	{
		//update 数据库
		import core.stdc.time;
		auto resStatus = PayBillService.getInstance.updateByWhere(["status":"1", "payment_time":to!string(time(null)), "trade_no":tradeNo],["id" :billId]);
		trace("update status ", resStatus);
		assert(resStatus, "update bill error "~billId);
	}
}
